using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Direct3D9;

using System.Xml;
using System.Xml.Serialization;

namespace Splino.D3D
{
    /// <summary>
    /// Contains the camera, matricies, a Direct3D Viewport, and
    /// viewport specific functions that
    /// make up a 3D Viewport contained in a Splino Panel
    /// </summary>
    public class SplinoViewport
    {
        /// <summary>
        /// A Direct3D viewport containing information about this viewport.
        /// </summary>
        Viewport _viewport;
        /// <summary>
        /// Gets or sets the Direct3D viewport containing information about this
        /// viewport.
        /// </summary>
        public Viewport Viewport
        {
            get { return _viewport; }
            set { _viewport = value; }
        }


        Viewport _viewportTopMost;
        /// <summary>
        /// Gets the viewport that is used while drawing the axes
        /// orientation indicator.  The MaxZ value of this 
        /// viewport is set to 0 so that the axes orientation
        /// inidcator always draw on top of the scene.
        /// </summary>
        public Viewport ViewportTopMost { get { return _viewportTopMost; } }

        Viewport _viewportBottomMost;
        /// <summary>
        /// Gets the viewport that is used while drawing objects below all
        /// other scene objects including the grid, axes, and locked objects.
        /// </summary>
        public Viewport ViewportBottomMost { get { return _viewportBottomMost; } }

        Vector3 _gridOrigin = new Vector3(0, 0, 0);
        /// <summary>
        /// Gets or sets a 3D vector representing the cut
        /// plane's origin
        /// </summary>
        public Vector3 GridOrigin
        {
            get { return _gridOrigin; }
            set { _gridOrigin = value; }
        }

        Vector3 _gridNormal;// = new Vector3(0, 0, 1);
        /// <summary>
        /// Gets or sets a 3D vector representing the
        /// cut plane's normal vector
        /// </summary>
        public Vector3 GridNormal
        {
            get { return _gridNormal; }
            set { _gridNormal = value; }
        }

        Vector3 _gridU;
        /// <summary>
        /// Gets a 3D vector representing the U axis of
        /// the cut plane
        /// </summary>
        public Vector3 GridU
        {
            get { return _gridU; }
            set { _gridU = value; }
        }

        Vector3 _gridV;
        /// <summary>
        /// Gets a sets a 3D vector representing the Y axis of
        /// the cut plane
        /// </summary>
        public Vector3 GridV
        {
            get { return Vector3.Cross(_gridU, _gridNormal); }
            set
            {
                _gridV = value;
                _gridNormal = Vector3.Cross(_gridV, _gridU);
            }
        }

        Vector3 _cameraPosition;   
        /// <summary>
        /// Gets or sets a 3D vector representing the Viewport
        /// camera's position
        /// </summary>
        public Vector3 CameraPosition
        {
            get { return _cameraPosition; }
            set { _cameraPosition = value; }
        }

        Vector3 _cameraTarget;
        /// <summary>
        /// Gets or sets a 3D vector representing the Viewport
        /// camera's look at target
        /// </summary>
        public Vector3 CameraTarget
        {
            get { return _cameraTarget; }
            set { _cameraTarget = value; }
        }

        /// <summary>
        /// Gets a 3D unit vector representing the direction
        /// the Viewport camera is pointing at
        /// </summary>
        [XmlIgnore()]
        public Vector3 CameraDirection      // Camera'a directional vector
        {
            get
            {
                return Vector3.Normalize(_cameraTarget - _cameraPosition);
            }
        }

        Vector3 _cameraUpVector;
        /// <summary>
        /// Gets or sets a 3D vector representing the Viewport
        /// camera's up direction
        /// </summary>
        public Vector3 CameraUpVector
        {
            get { return _cameraUpVector; }
            set { _cameraUpVector = value; }
        }

        float _fieldOfViewY;
        /// <summary>
        /// Gets or sets a the Viewport camera's len angle 
        /// in the y direction.  This property is valid only
        /// for viewports containing a perspective camera
        /// </summary>
        public float FieldOfViewY
        {
            get { return _fieldOfViewY; }
            set { _fieldOfViewY = value; }
        }

        /// <summary>
        /// Gets the aspect ratio of the viewport
        /// </summary>
        [XmlIgnore()]
        public float AspectRatio              
        {
            get { return (float)ViewportWindow.PresentationPanel.Width / (float)ViewportWindow.PresentationPanel.Height; }
        }

        float _width;
        /// <summary>
        /// Gets or sets the width of the viewport/camera in world
        /// units.  This property is valid only for viewports 
        /// containing an orthagonal camera
        /// </summary>
        public float Width
        {
            get { return _width; }
            set { _width = value; }
        }

        /// <summary>
        /// Gets the viewport/camera height in world units.  This
        /// property is only valid for viewports with an orthagonal
        /// camera.  Use the Width property to adjust this property
        /// </summary>
        [XmlIgnore()]
        public float Height
        {
            get { return ((float)ViewportWindow.PresentationPanel.Height / (float)ViewportWindow.PresentationPanel.Width) * _width; }
        }

        float _znearPlane;
        /// <summary>
        /// Gets or sets the distance of the Z buffer's near plane
        /// </summary>
        public float ZNearPlane
        {
            get { return _znearPlane; }
            set { _znearPlane = value; }
        }

        float _zfarPlane;
        /// <summary>
        /// Gets or sets the distance of the Z buffer's far plane
        /// </summary>
        public float ZFarPlane
        {
            get { return _zfarPlane; }
            set { _zfarPlane = value; }
        }

        CameraType _cameraType;
        /// <summary>
        /// Gets or sets an enumerator representing which Splino
        /// Panel the Splino Viewport belongs to
        /// </summary>
        public CameraType CameraType
        {
            get { return _cameraType; }
            set { _cameraType = value; }
        }

        Matrix _matrixView;
        /// <summary>
        /// Gets or sets the camera'a view matrix
        /// </summary>
        [XmlIgnore()]
        public Matrix MatrixView
        {
            get { return _matrixView; }
            set { _matrixView = value; }
        }

        Matrix _matrixProjection;
        /// <summary>
        /// Gets or sets the camera's projection matrix
        /// </summary>
        [XmlIgnore()]
        public Matrix MatrixProjection
        {
            get { return _matrixProjection; }
            set { _matrixProjection = value; }
        }

        /// <summary>
        /// Translation matrix for positioning axes orientation indicator
        /// </summary>
        Matrix _matrixViewAxesOrientation;

        Vector3 _xAxisIndicatorLabelLocation;
        Vector3 _yAxisIndicatorLabelLocation;
        Vector3 _zAxisIndicatorLabelLocation;

        bool _drawxAxisIndicator;
        bool _drawyAxisIndicator;
        bool _drawzAxisIndicator;

        /// <summary>
        /// Enumerator for handling the draw order of the axes orientation indicator
        /// </summary>
        AxesDrawOrder _axesDrawOrder;

        /// <summary>
        /// Number of units grid extends from origin
        /// </summary>
        int _gridSize = 10;

        /// <summary>
        /// Vertex Buffer for major grid plane
        /// </summary>
        VertexBuffer _vbMajorGrid;
        /// <summary>
        /// Array holding major grid vertices
        /// </summary>
        CustomVertex.PositionColored[]
            _vertsMajorGrid;

        /// <summary>
        /// Vertex Buffer for minor grid plane
        /// </summary>
        VertexBuffer _vbMinorGrid;
        /// <summary>
        /// Array holding minor grid vertices
        /// </summary>
        CustomVertex.PositionColored[]
            _vertsMinorGrid;

        VertexBuffer _vbXAxis;
        CustomVertex.PositionColored[]
            _vertsXAxis;

        VertexBuffer _vbYAxis;
        CustomVertex.PositionColored[]
            _vertsYAxis;

        VertexBuffer _vbZAxis;
        CustomVertex.PositionColored[]
            _vertsZAxis;

        Matrix _matrixWorld = Matrix.Identity;
        /// <summary>
        /// Gets or sets the camera's world matrix
        /// </summary>
        [XmlIgnore()]
        public Matrix MatrixWorld
        {
            get { return _matrixWorld; }
            set { _matrixWorld = value; }
        }

        float _unitsPerPixelZNear;
        /// <summary>
        /// Gets the width/height of each pixel on the Z buffer's
        /// near plane in world units
        /// </summary>
        [XmlIgnore()]
        public float UnitsPerPixelZNear
        {
            get { return _unitsPerPixelZNear; }
        }

        Vector3 _screenPixelToCutPlaneXDirection;
        /// <summary>
        /// Gets a 3D vector with a direction of the cut plane's
        /// X axis and the magnitude of the width of a pixel on
        /// the cut plane in world units.  Used to produce
        /// consistent mouse panning rates.
        /// </summary>
        [XmlIgnore()]
        public Vector3 ScreenPixelToCutPlaneXDirection
        {
            get { return _screenPixelToCutPlaneXDirection; }
        }

        Vector3 _screenPixelToCutPlaneYDirection;
        /// <summary>
        /// Gets a 3D vector with a direction of the cut plane's
        /// Y axis and the magnitude of the width of a pixel on
        /// the cut plane in world units.  Used to produce
        /// consistent mouse panning rates.
        /// </summary>
        [XmlIgnore()]
        public Vector3 ScreenPixelToCutPlaneYDirection
        {
            get { return _screenPixelToCutPlaneYDirection; }
        }

        bool _paused = false;
        /// <summary>
        /// Gets or sets whether rendering is paused on this viewport.
        /// </summary>
        public bool Paused
        {
            get { return _paused; }
            set { _paused = value; }
        }

        CustomVertex.PositionColored[] _xAxisGuideVertices = new CustomVertex.PositionColored[2];
        CustomVertex.PositionColored[] _yAxisGuideVertices = new CustomVertex.PositionColored[2];
        CustomVertex.PositionColored[] _zAxisGuideVertices = new CustomVertex.PositionColored[2];

        VertexBuffer _xAxisGuideVertexBuffer;
        VertexBuffer _yAxisGuideVertexBuffer;
        VertexBuffer _zAxisGuideVertexBuffer;

        Vector3 _cameraStartPosition;
        /// <summary>
        /// Gets or sets a 3D vector used to keep track of the camera's starting
        /// position before a camera transformation operation
        /// </summary>
        [XmlIgnore()]
        public Vector3 CameraStartPosition
        {
            get { return _cameraStartPosition; }
            set { _cameraStartPosition = value; }
        }

        Vector3 _cameraStartTarget;
        /// <summary>
        /// Gets or sets a 3D vector used to keep track of the camera's starting
        /// look at target before a camera transformation operation
        /// </summary>
        [XmlIgnore()]
        public Vector3 CameraStartTarget
        {
            get { return _cameraStartTarget; }
            set { _cameraStartTarget = value; }
        }

        float _zoomingDetentFactor = 0.15f;
        /// <summary>
        /// Gets the rate at which a viewport is interactively zoomed
        /// with the mouse wheel
        /// </summary>
        [XmlIgnore()]
        public float ZoomingFactor
        {
            get { return _zoomingDetentFactor; }
        }

        List<Vector3> _angleSnapPoints = new List<Vector3>();
        /// <summary>
        /// Gets a list of units vectors representing the angle snap lines.
        /// </summary>
        [XmlIgnore()]
        public List<Vector3> AngleSnapVectors
        {
            get { return _angleSnapPoints; }
        }

        /// <summary>
        /// Gets the rate at which a viewport is interactively zoomed
        /// by dragging the mouse up or down
        /// </summary>
        float _zoomingCursorFactor = 0.01f;

        SplinoWindow _splinoWindow;
        /// <summary>
        /// Gets the Splino Window that contains the viewport
        /// </summary>
        [XmlIgnore()]
        public SplinoWindow ViewportWindow
        {
            get { return _splinoWindow; }
            set { _splinoWindow = value; }
        }

        /// <summary>
        /// Swapchain for the viewport
        /// </summary>
        SwapChain _swapChain = null;
        /// <summary>
        /// Gets or sets the viewport's swap chain.  Used for multiple viewport rendering.
        /// </summary>
        [XmlIgnore()]
        public SwapChain SwapChain
        {
            get { return _swapChain; }
            set { _swapChain = value; }
        }

        Surface _surfaceBackBuffer;
        /// <summary>
        /// Gets or sets the viewport's backbuffer surface
        /// </summary>
        [XmlIgnore()]
        public Surface BackBuffer
        {
            get { return _surfaceBackBuffer; }
            set { _surfaceBackBuffer = value; }
        }

        Surface _depthStencilBuffer;
        /// <summary>
        /// Gets or sets the viewport's depth stencil buffer
        /// </summary>
        [XmlIgnore()]
        public Surface DepthStencilBuffer
        {
            get { return _depthStencilBuffer; }
            set { _depthStencilBuffer = value; }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public SplinoViewport() : this(CameraType.Orthographic, Vector3.UnitX, Vector3.UnitY) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="cameraType">An enumerator indicating which default
        /// viewport and camera settings to use.</param>
        /// <param name="gridU">The cut plane's U axis</param>
        /// <param name="gridV">The cut plane's V axis</param>
        public SplinoViewport(CameraType cameraType, Vector3 gridU, Vector3 gridV)
        {
            _cameraType = cameraType;

            _viewport = new Viewport();
            _viewport.X = 0;
            _viewport.Y = 0;
            _viewport.MinZ = 0f;
            _viewport.MaxZ = 1f;

            _viewportTopMost = new Viewport();
            _viewportTopMost.X = 0;
            _viewportTopMost.Y = 0;
            _viewportTopMost.MinZ = 0f;
            _viewportTopMost.MaxZ = 0f;

            _viewportBottomMost = new Viewport();
            _viewportBottomMost.X = 0;
            _viewportBottomMost.Y = 0;
            _viewportBottomMost.MinZ = 1f;
            _viewportBottomMost.MaxZ = 1f;

            _gridU = gridU;
            _gridV = gridV;
            _gridNormal = Vector3.Cross(_gridU, _gridV);
        }

        /// <summary>
        /// Adjusts the camera and matricies when the Splino Panel
        /// containing the viewport is resized
        /// </summary>
        public void UpdateDimensions()
        {
            _viewport.Height = ViewportWindow.PresentationPanel.Height;
            _viewport.Width = ViewportWindow.PresentationPanel.Width;

            _viewportTopMost.Height = ViewportWindow.PresentationPanel.Height;
            _viewportTopMost.Width = ViewportWindow.PresentationPanel.Width;

            _viewportBottomMost.Height = ViewportWindow.PresentationPanel.Height;
            _viewportBottomMost.Width = ViewportWindow.PresentationPanel.Width;

            switch (_cameraType)
            {
                case CameraType.Perspective:
                    _matrixProjection = Matrix.PerspectiveFovLH(
                        _fieldOfViewY,
                        (float)ViewportWindow.PresentationPanel.Width / (float)ViewportWindow.PresentationPanel.Height,
                        _znearPlane,
                        _zfarPlane);
                    break;
                default:
                    _matrixProjection = Matrix.OrthoLH(
                        _width,
                        ((float)ViewportWindow.PresentationPanel.Height / (float)ViewportWindow.PresentationPanel.Width) * _width,
                        _znearPlane,
                        _zfarPlane);
                    break;
            }

            UpdateMatrices();
            if (DeviceManager.Initialized)
                LayoutAxesOrientationGuide();
        }

        /// <summary>
        /// Updates the viewport's matricies to reflect a camera transformation
        /// </summary>
        public void UpdateMatrices()
        {
            MatrixView = Matrix.LookAtLH(
                _cameraPosition,
                _cameraTarget,
                _cameraUpVector);

            switch (_cameraType)
            {
                case CameraType.Perspective:
                    _matrixProjection = Matrix.PerspectiveFovLH(
                        _fieldOfViewY,
                        (float)ViewportWindow.PresentationPanel.Width / (float)ViewportWindow.PresentationPanel.Height,
                        _znearPlane,
                        _zfarPlane);
                    _width = UnitsPerPixel(_cameraTarget) * ViewportWindow.PresentationPanel.Width;
                    break;
                default:
                    _matrixProjection = Matrix.OrthoLH(
                        _width,
                        ((float)ViewportWindow.PresentationPanel.Height / (float)ViewportWindow.PresentationPanel.Width) * _width,
                        _znearPlane,
                        _zfarPlane);
                    break;
            }

            _matrixViewAxesOrientation = Matrix.Translation(new Vector3(
                -((((float)ViewportWindow.PresentationPanel.Width) / 2) - 30) / (((float)ViewportWindow.PresentationPanel.Width) / 2),
                -((((float)ViewportWindow.PresentationPanel.Height) / 2) - 30) / (((float)ViewportWindow.PresentationPanel.Height) / 2),
                0));

            UpdateUnitsPerPixelZNear();
        }

        /// <summary>
        /// Converts a Splino Panel orientated screen coordinate to
        /// a 3D vector representing the world coordinate of where
        /// the pixel intersects the cut plane.
        /// </summary>
        /// <param name="x">The X location of the Splino Panel
        /// screen coordinate</param>
        /// <param name="y">The Y location of the Splino Panel
        /// screen coordinate</param>
        /// <returns>A 3D vector representing the cut plane
        /// world coordinate of a Splino Viewport screen
        /// pixel coordinate</returns>
        public Vector3 ScreenToWorldGrid(int x, int y)
        {
            Vector3 input1 = new Vector3(x, y, 1);
            Vector3 output1 = new Vector3();

            output1 = Vector3.Unproject(
                input1,
                _viewport,
                _matrixProjection,
                _matrixView,
                _matrixWorld);

            Vector3 lineOrigin = new Vector3();

            switch (_cameraType)
            {
                case CameraType.Perspective:
                    lineOrigin = _cameraPosition;
                    break;
                default:
                    Vector3 input2 = new Vector3(x, y, 0);
                    Vector3 output2 = new Vector3();

                    output2 = Vector3.Unproject(
                        input2,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);
                    lineOrigin = output2;
                    break;
            }
            Vector3 lineDirection = output1 - lineOrigin;

            Vector3 intersection = new Vector3();

            intersection = lineOrigin +
                (Vector3.Dot((_gridOrigin - lineOrigin), _gridNormal) /
                Vector3.Dot(lineDirection, _gridNormal)) *
                lineDirection;

            return intersection;
        }

        /// <summary>
        /// Converts a Splino Panel's 2D screen coordinate to a
        /// 3D vector representing the world coorindate of where that
        /// pixel intersects the viewport camera's look at target plane.
        /// </summary>
        /// <param name="x">The X location of the Splino Panel
        /// screen coordinate</param>
        /// <param name="y">The Y location of the Splno Panel
        /// screen cooridnate</param>
        /// <returns>3D world coordinates of the converted
        /// 2D Splino Panel screen coordinate</returns>
        public Vector3 ScreenToTargetPlane(float x, float y)
        {
            Vector3 farinput = new Vector3(x, y, 1f);
            Vector3 faroutput = new Vector3();

            Vector3 nearinput = new Vector3(x, y, 0f);
            Vector3 nearoutput = new Vector3();

            faroutput = Vector3.Unproject(
                farinput,
                _viewport,
                _matrixProjection,
                MatrixView,
                MatrixWorld);

            nearoutput = Vector3.Unproject(
                nearinput,
                _viewport,
                _matrixProjection,
                MatrixView,
                MatrixWorld);

            Vector3 lineDirection = faroutput - nearoutput;
            Vector3 lineOrigin = nearoutput;

            Vector3 intersection = lineOrigin +
                (Vector3.Dot((_cameraTarget - _cameraPosition), CameraDirection) /
                Vector3.Dot(lineDirection, CameraDirection)) *
                lineDirection;

            return intersection;
        }

        /// <summary>
        /// Converts a Splino Panel's 2D screen coordinate to a
        /// 3D vector representing the world coordinate of where that
        /// pixel intersects the viewport camera's near plane.
        /// </summary>
        /// <param name="x">The X location of the Splino Panel
        /// screen coorindate</param>
        /// <param name="y">The Y location of the Splno Panel
        /// screen cooridnate</param>
        /// <returns>3D world coordinates of the converted
        /// 2D Splino Panel screen coordinate</returns>
        public Vector3 ScreenToCameraNearPlane(float x, float y)
        {
            Vector3 nearinput = new Vector3(x, y, 0f);

            return Vector3.Unproject(
                nearinput,
                _viewport,
                _matrixProjection,
                MatrixView,
                MatrixWorld);
        }

        /// <summary>
        /// Converts a Splino Panel's 2D screen coordinate to a
        /// 3D vector representing the world coordinate of where that
        /// pixel intersects the viewport camera's far plane.
        /// </summary>
        /// <param name="x">The X location of the Splino Panel
        /// screen coorindate</param>
        /// <param name="y">The Y location of the Splno Panel
        /// screen cooridnate</param>
        /// <returns>3D world coordinates of the converted
        /// 2D Splino Panel screen coordinate</returns>
        public Vector3 ScreenToCameraFarPlane(float x, float y)
        {
            Vector3 farInput = new Vector3(x, y, 1f);

            return Vector3.Unproject(
                farInput,
                _viewport,
                _matrixProjection,
                MatrixView,
                MatrixWorld);
        }

        /// <summary>
        /// Returns an open ended mesh representing the volume that the selection rectangle encompasses 
        /// </summary>
        /// <returns>A mesh representing the volume that the selection rectangle encompasses</returns>
        public Mesh SelectionFustrumMesh()
        {
            Vector3 selectionUpperLeftNear = ScreenToCameraNearPlane(
                SceneManager.Instance.SelectionStartPoint.X,
                SceneManager.Instance.SelectionStartPoint.Y);

            Vector3 selectionUpperLeftFar = ScreenToCameraFarPlane(
                SceneManager.Instance.SelectionStartPoint.X,
                SceneManager.Instance.SelectionStartPoint.Y);

            Vector3 selectionUpperRightNear = ScreenToCameraNearPlane(
                SceneManager.Instance.SelectionEndPoint.X,
                SceneManager.Instance.SelectionStartPoint.Y);

            Vector3 selectionUpperRightFar = ScreenToCameraFarPlane(
                SceneManager.Instance.SelectionEndPoint.X,
                SceneManager.Instance.SelectionStartPoint.Y);

            Vector3 selectionLowerRightNear = ScreenToCameraNearPlane(
                SceneManager.Instance.SelectionEndPoint.X,
                SceneManager.Instance.SelectionEndPoint.Y);

            Vector3 selectionLowerRightFar = ScreenToCameraFarPlane(
                SceneManager.Instance.SelectionEndPoint.X,
                SceneManager.Instance.SelectionEndPoint.Y);

            Vector3 selectionLowerLeftNear = ScreenToCameraNearPlane(
                SceneManager.Instance.SelectionStartPoint.X,
                SceneManager.Instance.SelectionEndPoint.Y);

            Vector3 selectionLowerLeftFar = ScreenToCameraFarPlane(
                SceneManager.Instance.SelectionStartPoint.X,
                SceneManager.Instance.SelectionEndPoint.Y);

            Mesh selectionRectangleFustrum = new Mesh(
                DeviceManager.Instance.Device, 8, 8, MeshFlags.VertexBufferManaged, VertexFormat.Position);

            CustomVertex.PositionOnly[] vertices = new CustomVertex.PositionOnly[]
            {
                new CustomVertex.PositionOnly(selectionUpperLeftNear),  // 0
                new CustomVertex.PositionOnly(selectionUpperRightNear), // 1
                new CustomVertex.PositionOnly(selectionLowerRightNear), // 2
                new CustomVertex.PositionOnly(selectionLowerLeftNear),  // 3
                new CustomVertex.PositionOnly(selectionUpperLeftFar),   // 4
                new CustomVertex.PositionOnly(selectionUpperRightFar),  // 5
                new CustomVertex.PositionOnly(selectionLowerRightFar),  // 6
                new CustomVertex.PositionOnly(selectionLowerLeftFar)    // 7
            };

            short[] indices = new short[]
            {
                0, 1, 5,
                0, 5, 4,
                1, 2, 6,
                1, 6, 5,
                2, 7, 6,
                2, 3, 7,
                3, 4, 7,
                3, 0, 4
            };

            DataStream stream = selectionRectangleFustrum.LockVertexBuffer(LockFlags.None);
            stream.WriteRange(vertices);
            selectionRectangleFustrum.UnlockVertexBuffer();
            stream = selectionRectangleFustrum.LockIndexBuffer(LockFlags.None);
            stream.WriteRange(indices);
            selectionRectangleFustrum.UnlockIndexBuffer();

            return selectionRectangleFustrum;
        }

        /// <summary>
        /// Recalculates the height/width of a pixel sized area of of the
        /// Z buffer's near plane
        /// </summary>
        public void UpdateUnitsPerPixelZNear()
        {
            _unitsPerPixelZNear = 0f;

            switch (_cameraType)
            {
                case CameraType.Perspective:

                    Vector3 topLeftCornerPixel = new Vector3(0f, 0f, 1f);
                    Vector3 bottomLeftCornerPixel = new Vector3(
                        0f,
                        (float)ViewportWindow.PresentationPanel.Height,
                        1f);

                    Vector3 topLeftCornerWorld = Vector3.Unproject(
                        topLeftCornerPixel,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    Vector3 bottomLeftCornerWorld = Vector3.Unproject(
                        bottomLeftCornerPixel,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    topLeftCornerWorld = _znearPlane * 2f *
                        Vector3.Normalize(topLeftCornerWorld - _cameraPosition);
                    bottomLeftCornerWorld = _znearPlane * 2f *
                        Vector3.Normalize(bottomLeftCornerWorld - _cameraPosition);

                    float cornerDistanceWorld = ((Vector3)(
                        topLeftCornerWorld - bottomLeftCornerWorld)).Length();

                    _unitsPerPixelZNear = 1f * (cornerDistanceWorld / (float)ViewportWindow.PresentationPanel.Height);

                    break;

                default:
                    _unitsPerPixelZNear = _width / (float)ViewportWindow.PresentationPanel.Width;
                    break;
            }
        }

        /// <summary>
        /// Calculates the width/height of a pixel in world coordinate units
        /// on a plane, given the plane's origin
        /// </summary>
        /// <param name="target">The plane's origin to calculate the
        /// units per pixel</param>
        /// <returns>The world coordinate units per pixel</returns>
        public float UnitsPerPixel(Vector3 target)
        {
            float unitsPerPixel = 0f;
            float targetDistance = ((Vector3)(Vector3.Subtract(target, _cameraPosition))).Length();

            switch (_cameraType)
            {
                case CameraType.Perspective:

                    Vector3 topLeftCornerPixel = new Vector3(0f, 0f, 1f);
                    Vector3 bottomLeftCornerPixel = new Vector3(
                        0f,
                        (float)ViewportWindow.PresentationPanel.Height,
                        1f);

                    Vector3 topLeftCornerWorld = Vector3.Unproject(
                        topLeftCornerPixel,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    Vector3 bottomLeftCornerWorld = Vector3.Unproject(
                        bottomLeftCornerPixel,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    topLeftCornerWorld = targetDistance *
                        Vector3.Normalize(topLeftCornerWorld - _cameraPosition);
                    bottomLeftCornerWorld = targetDistance *
                        Vector3.Normalize(bottomLeftCornerWorld - _cameraPosition);

                    float cornerDistanceWorld = ((Vector3)(
                        topLeftCornerWorld - bottomLeftCornerWorld)).Length();

                    unitsPerPixel = (cornerDistanceWorld / (float)ViewportWindow.PresentationPanel.Height);

                    break;

                default:
                    unitsPerPixel = _width / (float)ViewportWindow.PresentationPanel.Width;
                    break;
            }

            return unitsPerPixel;
        }

        /// <summary>
        /// Updates the two 3D vector used to keep panning rates
        /// consistent at different zoom levels
        /// </summary>
        public void UpdatePanVectors()
        {
            Vector3 originPixel = Vector3.Project(
                _cameraTarget,
                _viewport,
                _matrixProjection,
                _matrixView,
                _matrixWorld);

            Vector3 xPixelWorld = ScreenToTargetPlane(originPixel.X + 1, originPixel.Y);
            Vector3 yPixelWorld = ScreenToTargetPlane(originPixel.X, originPixel.Y + 1);

            _screenPixelToCutPlaneXDirection = xPixelWorld - _cameraTarget;
            _screenPixelToCutPlaneYDirection = yPixelWorld - _cameraTarget;
        }

        /// <summary>
        /// Creates the line segments and text labels that make up the axes
        /// orientation guide located in the bottom left corner of the viewport
        /// </summary>
        public void LayoutAxesOrientationGuide()
        {
            Matrix newProjection = _matrixProjection;

            Vector3 guideOrigin = ScreenToWorldGrid(
                ViewportWindow.PresentationPanel.Width / 2,
                ViewportWindow.PresentationPanel.Height / 2);
            float guideLength = UnitsPerPixel(guideOrigin) * 30;

            _xAxisGuideVertices[0].Position = guideOrigin;
            _xAxisGuideVertices[1].X = guideOrigin.X + guideLength;
            _xAxisGuideVertices[1].Y = guideOrigin.Y;
            _xAxisGuideVertices[1].Z = guideOrigin.Z;
            _xAxisGuideVertices[0].Color = Palette.splinoX.ToArgb();
            _xAxisGuideVertices[1].Color = Palette.splinoX.ToArgb();

            _yAxisGuideVertices[0].Position = guideOrigin;
            _yAxisGuideVertices[1].X = guideOrigin.X;
            _yAxisGuideVertices[1].Y = guideOrigin.Y + guideLength;
            _yAxisGuideVertices[1].Z = guideOrigin.Z;
            _yAxisGuideVertices[0].Color = Palette.splinoY.ToArgb();
            _yAxisGuideVertices[1].Color = Palette.splinoY.ToArgb();

            _zAxisGuideVertices[0].Position = guideOrigin;
            _zAxisGuideVertices[1].X = guideOrigin.X;
            _zAxisGuideVertices[1].Y = guideOrigin.Y;
            _zAxisGuideVertices[1].Z = guideOrigin.Z + guideLength;
            _zAxisGuideVertices[0].Color = Palette.splinoZ.ToArgb();
            _zAxisGuideVertices[1].Color = Palette.splinoZ.ToArgb();

            if (_xAxisGuideVertexBuffer == null ||
                _yAxisGuideVertexBuffer == null ||
                _zAxisGuideVertexBuffer == null)
                ResetAxesGuide();

            DataStream stream = _xAxisGuideVertexBuffer.Lock(0, 0, LockFlags.None);
            stream.WriteRange(_xAxisGuideVertices);
            _xAxisGuideVertexBuffer.Unlock();

            stream = _yAxisGuideVertexBuffer.Lock(0, 0, LockFlags.None);
            stream.WriteRange(_yAxisGuideVertices);
            _yAxisGuideVertexBuffer.Unlock();

            stream = _zAxisGuideVertexBuffer.Lock(0, 0, LockFlags.None);
            stream.WriteRange(_zAxisGuideVertices);
            _zAxisGuideVertexBuffer.Unlock();

            float xLength = ((Vector3)(_xAxisGuideVertices[1].Position - _cameraPosition)).LengthSquared();
            float yLength = ((Vector3)(_yAxisGuideVertices[1].Position - _cameraPosition)).LengthSquared();
            float zLength = ((Vector3)(_zAxisGuideVertices[1].Position - _cameraPosition)).LengthSquared();

            _axesDrawOrder = AxesDrawOrder.yxz;

            if (xLength > yLength)
            {
                if (xLength > zLength)
                {
                    if (yLength > zLength)
                        _axesDrawOrder = AxesDrawOrder.xyz;
                    else
                        _axesDrawOrder = AxesDrawOrder.xzy;
                }
                else
                    _axesDrawOrder = AxesDrawOrder.zxy;
            }
            else if (yLength > zLength)
                _axesDrawOrder = AxesDrawOrder.yzx;
            else
                _axesDrawOrder = AxesDrawOrder.zyx;

            _xAxisIndicatorLabelLocation = Vector3.Project(
                _xAxisGuideVertices[1].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            _yAxisIndicatorLabelLocation = Vector3.Project(
                _yAxisGuideVertices[1].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            _zAxisIndicatorLabelLocation = Vector3.Project(
                _zAxisGuideVertices[1].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            Vector3 xAxisIndicatorLocation = Vector3.Project(
                _xAxisGuideVertices[0].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            Vector3 yAxisIndicatorLocation = Vector3.Project(
                _yAxisGuideVertices[0].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            Vector3 zAxisIndicatorLocation = Vector3.Project(
                _zAxisGuideVertices[0].Position,
                _viewport,
                _matrixProjection * _matrixViewAxesOrientation,
                _matrixView,
                _matrixWorld);

            if (((Vector3)(_xAxisIndicatorLabelLocation - xAxisIndicatorLocation)).LengthSquared() > 0.05f)
                _drawxAxisIndicator = true;
            else
                _drawxAxisIndicator = false;

            if (((Vector3)(_yAxisIndicatorLabelLocation - yAxisIndicatorLocation)).LengthSquared() > 0.05f)
                _drawyAxisIndicator = true;
            else
                _drawyAxisIndicator = false;

            if (((Vector3)(_zAxisIndicatorLabelLocation - zAxisIndicatorLocation)).LengthSquared() > 0.05f)
                _drawzAxisIndicator = true;
            else
                _drawzAxisIndicator = false;
        }

        /// <summary>
        /// Resets the vertex buffers for the axes orientation indicator
        /// </summary>
        public void ResetAxesGuide()
        {
            if (_xAxisGuideVertexBuffer != null)
                _xAxisGuideVertexBuffer.Dispose();
            _xAxisGuideVertexBuffer = new VertexBuffer(
                DeviceManager.Instance.Device,
                CustomVertex.PositionColored.SizeInBytes * 2,
                Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);

            if (_yAxisGuideVertexBuffer != null)
                _yAxisGuideVertexBuffer.Dispose();
            _yAxisGuideVertexBuffer = new VertexBuffer(
                DeviceManager.Instance.Device,
                CustomVertex.PositionColored.SizeInBytes * 2,
                Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);

            if (_zAxisGuideVertexBuffer != null)
                _zAxisGuideVertexBuffer.Dispose();
            _zAxisGuideVertexBuffer = new VertexBuffer(
                DeviceManager.Instance.Device,
                CustomVertex.PositionColored.SizeInBytes * 2,
                Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);

            DataStream stream = _xAxisGuideVertexBuffer.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * 2, LockFlags.None);
            stream.WriteRange(_xAxisGuideVertices);
            _xAxisGuideVertexBuffer.Unlock();

            stream = _yAxisGuideVertexBuffer.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * 2, LockFlags.None);
            stream.WriteRange(_yAxisGuideVertices);
            _yAxisGuideVertexBuffer.Unlock();

            stream = _zAxisGuideVertexBuffer.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * 2, LockFlags.None);
            stream.WriteRange(_zAxisGuideVertices);
            _zAxisGuideVertexBuffer.Unlock();
        }

        /// <summary>
        /// Draws the three axes guides that indicate the orientation of the
        /// viewport camera.  Hides the axis guide if it is nearly orthagonal
        /// to the camera's look at target plane
        /// </summary>
        public void DrawAxesGuide()
        {
            DeviceManager.Instance.Device.SetTransform(
                TransformState.Projection,
                DeviceManager.Instance.Device.GetTransform(TransformState.Projection) * 
                _matrixViewAxesOrientation);

            switch (_axesDrawOrder)
            {
                case AxesDrawOrder.xyz:
                    DrawXAxesGuide();
                    DrawZAxesGuide();
                    DrawYAxesGuide();
                    break;
                case AxesDrawOrder.xzy:
                    DrawXAxesGuide();
                    DrawZAxesGuide();
                    DrawYAxesGuide();
                    break;
                case AxesDrawOrder.yxz:
                    DrawYAxesGuide();
                    DrawXAxesGuide();
                    DrawZAxesGuide();
                    break;
                case AxesDrawOrder.yzx:
                    DrawYAxesGuide();
                    DrawZAxesGuide();
                    DrawXAxesGuide();
                    break;
                case AxesDrawOrder.zxy:
                    DrawZAxesGuide();
                    DrawXAxesGuide();
                    DrawYAxesGuide();
                    break;
                case AxesDrawOrder.zyx:
                    DrawZAxesGuide();
                    DrawYAxesGuide();
                    DrawXAxesGuide();
                    break;
            }

            DeviceManager.Instance.Device.SetTransform(TransformState.Projection, _matrixProjection);
        }

        /// <summary>
        /// Draws the line segment and text label to indicate the camera's
        /// orientation to the X axis
        /// </summary>
        private void DrawXAxesGuide()
        {
            if (_drawxAxisIndicator)
            {
                DeviceManager.Instance.AxesGuideFont.DrawString(
                    null,
                    "x",
                    (int)_xAxisIndicatorLabelLocation.X,
                    (int)_xAxisIndicatorLabelLocation.Y - 16,
                    Palette.splinoX);

                DeviceManager.Instance.Device.SetStreamSource(
                    0, _xAxisGuideVertexBuffer, 0, CustomVertex.PositionColored.SizeInBytes);
                DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
            }
        }

        /// <summary>
        /// Draws the line segment and text label to indicate the camera's
        /// orientation to the Y axis
        /// </summary>
        private void DrawYAxesGuide()
        {
            if (_drawyAxisIndicator)
            {
                DeviceManager.Instance.AxesGuideFont.DrawString(
                    null,
                    "y",
                    (int)_yAxisIndicatorLabelLocation.X,
                    (int)_yAxisIndicatorLabelLocation.Y - 16,
                    Palette.splinoY);

                DeviceManager.Instance.Device.SetStreamSource(
                    0, _yAxisGuideVertexBuffer, 0, CustomVertex.PositionColored.SizeInBytes);
                DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
            }
        }

        /// <summary>
        /// Draws the line segment and text label to indicate the camera's
        /// orientation to the Z axis
        /// </summary>
        private void DrawZAxesGuide()
        {
            if (_drawzAxisIndicator)
            {
                DeviceManager.Instance.AxesGuideFont.DrawString(
                    null,
                    "z",
                    (int)_zAxisIndicatorLabelLocation.X,
                    (int)_zAxisIndicatorLabelLocation.Y - 16,
                    Palette.splinoZ);

                DeviceManager.Instance.Device.SetStreamSource(
                    0, _zAxisGuideVertexBuffer, 0, CustomVertex.PositionColored.SizeInBytes);
                DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
            }
        }

        /// <summary>
        /// Sets the intial 3D camera vectors that are referenced during
        /// camera panning
        /// </summary>
        public void BeginPan()
        {
            _cameraStartPosition = _cameraPosition;
            _cameraStartTarget = _cameraTarget;
        }

        /// <summary>
        /// Pans the viewport's camera based on the distance the
        /// mouse has traveled
        /// </summary>
        /// <param name="dx">The number of horizontal pixels the
        /// mouse has moved</param>
        /// <param name="dy">The number of vertical pixels the 
        /// mouse has moved</param>
        public void Pan(int dx, int dy)
        {
            Vector3 cameraTranslation = dx * _screenPixelToCutPlaneXDirection +
                dy * _screenPixelToCutPlaneYDirection;

            _cameraPosition = _cameraStartPosition - cameraTranslation;
            CameraTarget = _cameraStartTarget - cameraTranslation;
            UpdateMatrices();
            LayoutAxesOrientationGuide();
            DeviceManager.Instance.RequestRender();
        }

        /// <summary>
        /// Sets the initial 3D camera vectors that are referenced
        /// during camera zooming
        /// </summary>
        public void BeginZoom()
        {
            _cameraStartPosition = _cameraPosition;
            _cameraStartTarget = _cameraTarget;
        }

        /// <summary>
        /// Zooms the camera an amount based on the number of mouse wheel
        /// clicks, and keeps the world coorindate point that the mouse is over 
        /// steady in the viewport during the zooming by panning the viewport
        /// </summary>
        /// <param name="x">The X location of the mouse in Splino Panel
        /// screen coordinates</param>
        /// <param name="y">The Y location of the mouse in Splino Panel
        /// screen coordinates</param>
        /// <param name="delta">The number of clicks the mouse wheel
        /// has been rolled</param>
        public void ZoomOneDetent(int x, int y, int delta)
        {
            delta = delta / 120;
            switch (_cameraType)
            {
                case CameraType.Perspective:
                    _cameraPosition = _cameraTarget + (_cameraPosition - _cameraTarget) * (1 - delta*_zoomingDetentFactor);
                    break;

                default:
                    Vector3 originalPixelPosition = new Vector3(x, y, 1);
                    Vector3 originalWorldPosition = Vector3.Unproject(
                        originalPixelPosition,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    _width *= (1 - delta * _zoomingDetentFactor);

                    UpdateMatrices();

                    Vector3 newPixelPosition = new Vector3(x, y, 1);
                    Vector3 newWorldPosition = Vector3.Unproject(
                        newPixelPosition,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    Vector3 cameraTranslate = newWorldPosition - originalWorldPosition;
                    _cameraTarget -= cameraTranslate;
                    _cameraPosition -= cameraTranslate;

                    break;
            }
            UpdateZPlanes();
            UpdateMatrices();
            LayoutAxesOrientationGuide();
            DeviceManager.Instance.RequestRender();
        }

        /// <summary>
        /// Zooms the viewport based on the number of vertical pixels
        /// the mouse has traveled, keeping the viewport world coordinate
        /// that the mouse is over, steady during the zoom by panning the
        /// viewport
        /// </summary>
        /// <param name="x">The X coorindate position of the mouse in
        /// Splino Panel screen coordinates</param>
        /// <param name="y">The Y coorindate position of the mouse in
        /// Splino Panel screen coorindates</param>
        /// <param name="ey">The number of vertical pixels the mouse
        /// has traveled</param>
        public void ZoomCursor(int x, int y, int ey)
        {
            switch (_cameraType)
            {
                case CameraType.Perspective:
                    _cameraPosition = _cameraTarget + (_cameraPosition - _cameraTarget) * (1 + ey * _zoomingCursorFactor);
                    break;

                default:
                    Vector3 originalPixelPosition = new Vector3(x, y, 1);
                    Vector3 originalWorldPosition = Vector3.Unproject(
                        originalPixelPosition,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    _width *= (1 + ey * _zoomingCursorFactor);

                    UpdateMatrices();

                    Vector3 newPixelPosition = new Vector3(x, y, 1);
                    Vector3 newWorldPosition = Vector3.Unproject(
                        newPixelPosition,
                        _viewport,
                        _matrixProjection,
                        _matrixView,
                        _matrixWorld);

                    Vector3 cameraTranslate = newWorldPosition - originalWorldPosition;
                    _cameraTarget -= cameraTranslate;
                    _cameraPosition -= cameraTranslate;

                    break;
            }
            UpdateZPlanes();
            UpdateMatrices();
            LayoutAxesOrientationGuide();
            DeviceManager.Instance.RequestRender();
        }

        /// <summary>
        /// Sets the initial 3D camera vectors that are referenced
        /// during viewport camera rotation
        /// </summary>
        public void BeginRotate()
        {
            _cameraStartPosition = _cameraPosition;
            _cameraStartTarget = _cameraTarget;
        }

        /// <summary>
        /// Rotates the viewport based on the distance the mouse
        /// has traveled
        /// </summary>
        /// <param name="ex">The number of horizontal pixels the
        /// mouse has traveled</param>
        /// <param name="ey">The number of vertical pixels the
        /// mouse has traveled</param>
        public void Rotate(int ex, int ey)
        {
            Vector3 cameraTargetToOrigin = _cameraPosition - _cameraTarget;

            Vector3 cameraRight = 
                Vector3.Cross(Vector3.Normalize(cameraTargetToOrigin), 
                CameraUpVector);

            Matrix yRotation = Matrix.RotationY(ex*0.01f);
            Matrix rightRotation = Matrix.RotationAxis(cameraRight, ey*0.01f);

            Matrix rotateTransform = rightRotation * yRotation;

            cameraTargetToOrigin = Vector3.TransformCoordinate(cameraTargetToOrigin, rotateTransform);
            CameraUpVector = Vector3.TransformCoordinate(CameraUpVector, rotateTransform);

            _cameraPosition = cameraTargetToOrigin + _cameraTarget;
            UpdateMatrices();
            LayoutAxesOrientationGuide();
            DeviceManager.Instance.RequestRender();
        }

        /// <summary>
        /// Adjusts the position of the Z buffer near and far planes
        /// based on the distance between the camera's position and its
        /// look at target
        /// </summary>
        public void UpdateZPlanes()
        {
            float positionTargetLength = ((Vector3)(_cameraPosition - _cameraTarget)).Length();
            _zfarPlane = 5 * positionTargetLength;
            _znearPlane = _zfarPlane * 0.00001f;

            UpdateUnitsPerPixelZNear();
        }

        /// <summary>
        /// Updates the position of the Point Object used to visually represent,
        /// in world coordinates, where the users's mouse is, given its
        /// 2D Splino Panel screen coordinates
        /// </summary>
        /// <param name="x">The X location of the mouse in Splino Panel
        /// screen coordiantes</param>
        /// <param name="y">The Y location of the mouse in Splino Panel
        /// screen coordinates</param>
        public void UpdateDrawPoint(int x, int y)
        {
            Vector3 updatedDrawPoint = ScreenToWorldGrid(x, y);

            // FIX - This function snaps to world coordinates
            // ScreenToWorldGrid should be renamed, and a proper
            // ScreenToGrid function created and used here

            if (SplinoForm.Instance.ObjectSnapSelected ||
                SplinoForm.Instance.ObjectCloseSplineSnapSelected)
                updatedDrawPoint = SnapToObject(updatedDrawPoint, x, y);

            if (!ViewportWindow.SnapLabel.Visible)
            {
                if (SplinoForm.Instance.GridSnapSelected &&
                    SplinoForm.Instance.AngleSnapSelected &&
                    SceneManager.Instance.BaseDrawPointInitialized)
                {
                    updatedDrawPoint = SnapToGridAndAngle(updatedDrawPoint);
                }
                else if (SplinoForm.Instance.GridSnapSelected)
                {
                    updatedDrawPoint = SnapToGrid(updatedDrawPoint);
                }
                else if (
                    SplinoForm.Instance.AngleSnapSelected &&
                    SceneManager.Instance.BaseDrawPointInitialized)
                {
                    updatedDrawPoint = SnapToAngle(updatedDrawPoint);
                }
            }

            if (SceneManager.Instance.CursorPointLinearConstraintStart.HasValue &&
                SceneManager.Instance.CursorPointLinearConstraintEnd.HasValue)
            {
                Vector3 drawLeg = updatedDrawPoint - SceneManager.Instance.CursorPointLinearConstraintStart.Value;
                Vector3 startLeg = SceneManager.Instance.CursorPointLinearConstraintEnd.Value - SceneManager.Instance.CursorPointLinearConstraintStart.Value;
                startLeg.Normalize();
                updatedDrawPoint = Vector3.Dot(drawLeg, startLeg) * startLeg + SceneManager.Instance.CursorPointLinearConstraintStart.Value;
            }

            SceneManager.Instance.CursorPoint = updatedDrawPoint;

            DeviceManager.Instance.RequestRender();
        }

        /// <summary>
        /// Takes the 3D cursor drawing point and snaps it to the world grid.
        /// </summary>
        /// <param name="screenToWorldGrid"></param>
        /// <returns></returns>
        private Vector3 SnapToGrid(Vector3 screenToWorldGrid)
        {
            float xComp = screenToWorldGrid.X * (1 / SplinoForm.Instance.GridSnapDistance);
            float yComp = screenToWorldGrid.Y * (1 / SplinoForm.Instance.GridSnapDistance);
            float zComp = screenToWorldGrid.Z * (1 / SplinoForm.Instance.GridSnapDistance);

            xComp = (float)Math.Round(xComp);
            yComp = (float)Math.Round(yComp);
            zComp = (float)Math.Round(zComp);

            xComp *= SplinoForm.Instance.GridSnapDistance;
            yComp *= SplinoForm.Instance.GridSnapDistance;
            zComp *= SplinoForm.Instance.GridSnapDistance;

            return new Vector3(xComp, yComp, zComp);
        }

        private Vector3 SnapToAngle(Vector3 screenToWorldGrid)
        {
            List<float> distances = new List<float>();
            List<Vector3> angleSnaps = new List<Vector3>();
            for (int i = 0; i < _angleSnapPoints.Count; i++)
            {
                float u;
                Vector3 basePoint = SceneManager.Instance.BaseDrawPoint;
                Vector3 anglePoint = _angleSnapPoints[i];
                Vector3 drawPoint = screenToWorldGrid;
                Vector3 drawBasePoint = drawPoint - basePoint;
                Vector3 angleBasePoint = anglePoint - basePoint;
                float abx = angleBasePoint.X;
                float aby = angleBasePoint.Y;
                float abz = angleBasePoint.Z;
                float dbx = drawBasePoint.X;
                float dby = drawBasePoint.Y;
                float dbz = drawBasePoint.Z;

                u = (abx * dbx + aby * dby + abz * dbz) / (abx * abx + aby * aby + abz * abz);

                if (u > 0)
                {
                    Vector3 angleSnap = basePoint + u * angleBasePoint;
                    distances.Add(((Vector3)(angleSnap - drawPoint)).LengthSquared());
                    angleSnaps.Add(angleSnap);
                }
            }

            float closestDistance = -1;
            int closestDistanceIndex = 0;
            for (int i = 0; i < distances.Count; i++)
            {
                if (distances[i] < closestDistance || closestDistance < 0)
                {
                    closestDistance = distances[i];
                    closestDistanceIndex = i;
                }
            }
            if (angleSnaps.Count > 0)
                return angleSnaps[closestDistanceIndex];
            else
                return screenToWorldGrid;
        }

        private Vector3 SnapToGridAndAngle(Vector3 screenToWorldGrid)
        {
            Vector3 gridSnap = SnapToGrid(screenToWorldGrid);
            Vector3 angleSnap = SnapToAngle(screenToWorldGrid);
            Vector3 angleGridDiff = angleSnap - gridSnap;
            Vector3 basePoint = SceneManager.Instance.BaseDrawPoint;
            Vector3 zComp = Vector3.Dot(_gridNormal, angleGridDiff) * _gridNormal;
            Vector3 adjAngleSnap = angleGridDiff - zComp;
            Vector3 adjBasePoint = SceneManager.Instance.BaseDrawPoint - zComp;

            float xGridProjection = Vector3.Dot((gridSnap - basePoint), _gridU);
            float yGridProjection = Vector3.Dot((gridSnap - basePoint), _gridV);
            float xAngleProjection = Vector3.Dot((angleSnap - basePoint), _gridU);
            float yAngleProjection = Vector3.Dot((angleSnap - basePoint), _gridV);

            Vector3 snapToY = (xGridProjection / xAngleProjection) * (angleSnap - basePoint) + basePoint;
            Vector3 snapToX = (yGridProjection / yAngleProjection) * (angleSnap - basePoint) + basePoint;

            if (xAngleProjection == 0 || xGridProjection == 0)
            {
                if (yGridProjection == 0 || yAngleProjection == 0)
                    return gridSnap;
                return snapToX;
            }
            else if (yGridProjection == 0 || yAngleProjection == 0)
                return snapToY;

            if (((Vector3)(snapToY - screenToWorldGrid)).LengthSquared() > 
                ((Vector3)(snapToX - screenToWorldGrid)).LengthSquared())
                return snapToX;
            return snapToY;
        }

        private Vector3 SnapToObject(Vector3 screenToWorldGrid, int x, int y)
        {
            List<ObjectSnap> objectSnaps;
            float closestSnapDistance = -1f;
            int closestSnapIndex = -1;
            ObjectSnapType highestPrioritySnap = ObjectSnapType.None;

            if (_cameraType == CameraType.Perspective)
            {
                objectSnaps = SceneManager.Instance.MasterSceneObject.SnapTest(
                    this,
                    CameraPosition,
                    Vector3.Subtract(ScreenToTargetPlane(x, y), CameraPosition));

                objectSnaps.AddRange(SceneManager.Instance.TempSceneObject.SnapTest(
                    this,
                    CameraPosition,
                    Vector3.Subtract(ScreenToTargetPlane(x, y), CameraPosition)));
            }
            else
            {
                objectSnaps = SceneManager.Instance.MasterSceneObject.SnapTest(
                    this,
                    ScreenToCameraNearPlane(x, y),
                    Vector3.Subtract(ScreenToTargetPlane(x, y), ScreenToCameraNearPlane(x, y)));

                objectSnaps.AddRange(SceneManager.Instance.TempSceneObject.SnapTest(
                    this,
                    ScreenToCameraNearPlane(x, y),
                    Vector3.Subtract(ScreenToTargetPlane(x, y), ScreenToCameraNearPlane(x, y))));
            }

            foreach (ObjectSnap objectSnap in objectSnaps)
            {
                if (SplinoForm.Instance.ObjectSnapTypeActive(objectSnap.ObjectSnapType) && 
                    objectSnap.ObjectSnapType <= highestPrioritySnap)
                {
                    float snapDistance = ((Vector3)(objectSnap.SnapLocation - CameraPosition)).LengthSquared();
                    if (snapDistance < closestSnapDistance || closestSnapDistance == -1)
                    {
                        closestSnapDistance = snapDistance;
                        closestSnapIndex = objectSnaps.IndexOf(objectSnap);
                        highestPrioritySnap = objectSnap.ObjectSnapType;
                    } 
                }
            }

            if (closestSnapIndex > -1)
            {
                ViewportWindow.ShowSnapLabel(objectSnaps[closestSnapIndex].ObjectSnapType, x + 5, y + 5);
                return objectSnaps[closestSnapIndex].SnapLocation;
            }

            ViewportWindow.SnapLabel.Hide();
            return screenToWorldGrid;
        }

        /// <summary>
        /// Updates the 3D camera vectors, matricies, and Z buffer near
        /// and far planes, after the viewport's initial variables are set.
        /// Used to setup the viewport after a splino.xml file is opened.
        /// </summary>
        public void ReloadViewport()
        {
            UpdateZPlanes();
            UpdateDimensions();
            _matrixWorld = Matrix.Identity;
            UpdateMatrices();
            UpdatePanVectors();
            LayoutAxesOrientationGuide();
        }

        /// <summary>
        /// Converts a 2D vector representing coordinates on the cut
        /// plane to a 3D vector representing that point in world
        /// coordinates
        /// </summary>
        /// <param name="gridPoint">The 2D cut plane coordinates</param>
        /// <returns>The 3D world coordinates</returns>
        public Vector3 GridToWorld(Vector2 gridPoint)
        {
            Vector3 xComponent = Vector3.Multiply(_gridU, gridPoint.X);
            Vector3 yComponent = Vector3.Multiply(_gridV, gridPoint.Y);
            return Vector3.Add(xComponent, yComponent);
        }

        /// <summary>
        /// Returns a normalized vector that points from a point in 
        /// 3D world space to its corresponding pixel.
        /// </summary>
        /// <param name="point">The point in 3D world space.</param>
        /// <returns>A 3D world space vector that points toward
        /// the pixel the parameter corresponds too</returns>
        public Vector3 PointCameraNormal(Vector3 point)
        {
            if (CameraType == CameraType.Perspective)
                return Vector3.Normalize(CameraPosition - point);
            else
                return CameraDirection;
        }

        /// <summary>
        /// Determines if a point exists within the area that the
        /// viewport's camera can see: its viewing fustrum.  Used for
        /// picking/selecting objects from a selection rectangle by
        /// cropping the viewport camera fustrum by the amount the 
        /// selection rectangle encompasses
        /// </summary>
        /// <param name="point">A 3D vector representing world
        /// coordinates</param>
        /// <returns>True if the point is in the camera's fustrum</returns>
        public bool PointInFustrum(Vector3 point)
        {
            Vector3 pointInCamCoords = point - _cameraPosition;

            float pointInCamCoordsZ = Vector3.Dot(pointInCamCoords, CameraDirection);
            float pointInCamCoordsY = Vector3.Dot(pointInCamCoords, _cameraUpVector);
            float pointInCamCoordsX = Vector3.Dot(pointInCamCoords,
                Vector3.Cross(CameraUpVector, CameraDirection));

            float height;
            if (_cameraType == CameraType.Perspective)
                height = pointInCamCoordsZ * 2 * (float)Math.Tan(_fieldOfViewY / 2);
            else
                height = Height;

            float width;
            if (_cameraType == CameraType.Perspective)
                width = height * AspectRatio;
            else
                width = _width;

            float heightStart = (height * (
                (((float)ViewportWindow.PresentationPanel.Height) - (float)SceneManager.Instance.SelectionStartPoint.Y)
                / ((float)ViewportWindow.PresentationPanel.Height))) - height / 2;
            float heightEnd = (height * (
                (((float)ViewportWindow.PresentationPanel.Height) - (float)SceneManager.Instance.SelectionEndPoint.Y)
                / ((float)ViewportWindow.PresentationPanel.Height))) - height / 2;
            float widthStart = width *
                (((float)SceneManager.Instance.SelectionStartPoint.X)
                / ((float)ViewportWindow.PresentationPanel.Width)) - width / 2;
            float widthEnd = width *
                (((float)SceneManager.Instance.SelectionEndPoint.X)
                / ((float)ViewportWindow.PresentationPanel.Width)) - width / 2;

            if (heightStart > heightEnd)
            {
                float temp = heightStart;
                heightStart = heightEnd;
                heightEnd = temp;
            }

            if (widthStart > widthEnd)
            {
                float temp = widthStart;
                widthStart = widthEnd;
                widthEnd = temp;
            }

            if (pointInCamCoordsY < heightStart || pointInCamCoordsY > heightEnd)
                return false;
            if (pointInCamCoordsX < widthStart || pointInCamCoordsX > widthEnd)
                return false;

            return true;
        }

        /// <summary>
        /// Computes the lines used for angle snapping
        /// </summary>
        public void ComputeAngleSnapPoints()
        {
            float degrees = SplinoForm.Instance.AngleSnapDegrees;
            int numLines = (int)Math.Round(360f / degrees);
            Vector3 basePoint = SceneManager.Instance.BaseDrawPoint;
            Vector3 baseVector = _gridV;
            _angleSnapPoints.Clear();

            for (int i = 0; i < numLines; i++)
            {
                Vector4 transformedVector =
                    Vector3.Transform(
                    baseVector,
                    Matrix.RotationAxis(_gridNormal, i * degrees * 0.0174532f));
                Vector3 transformedPoint = 
                    basePoint +
                    new Vector3(
                        transformedVector.X,
                        transformedVector.Y,
                        transformedVector.Z);
                _angleSnapPoints.Add(transformedPoint);
            }
        }

        /// <summary>
        /// Cleans up device memory use for object specific to this viewport
        /// </summary>
        public void DisposeViewportDeviceResources()
        {
            if (_swapChain != null)
                _swapChain.Dispose();
            if (_surfaceBackBuffer != null)
                _surfaceBackBuffer.Dispose();

            _vbMajorGrid.Dispose();
            _vbMinorGrid.Dispose();
            _vbXAxis.Dispose();
            _vbYAxis.Dispose();
            _vbZAxis.Dispose();
        }

        /// <summary>
        /// Sets up the grid that the user can snap to
        /// </summary>
        public void LayoutGrid()
        {
            // Minor Grid = 
            // (_gridSize * 2 for negative side of origin * 10 lines per unit 
            // * 2 Verts per line + 2 verts for the 0 line)
            // * 2 for the lines in the opposite direction
            _vertsMinorGrid = new CustomVertex.PositionColored[_gridSize * 80 + 4];

            for (int i = 0; i < _gridSize * 40 + 2; i += 2)
            {
                float d = (float)i * 0.5f;

                _vertsMinorGrid[i].Position = _gridU * (0.1f * d - _gridSize);
                _vertsMinorGrid[i].Position += _gridV * _gridSize;
                _vertsMinorGrid[i+1].Position = _gridU * (0.1f * d - _gridSize);
                _vertsMinorGrid[i+1].Position += -_gridV * _gridSize;

                _vertsMinorGrid[i].Color = Splino.Palette.splino01.ToArgb();
                _vertsMinorGrid[i + 1].Color = Splino.Palette.splino01.ToArgb();
            }

            for (int i = _gridSize * 40 + 2; i < _gridSize * 80 + 4; i += 2)
            {
                float d = ((float)(i - (_gridSize * 40 + 2))) * 0.5f;

                _vertsMinorGrid[i].Position = _gridU * _gridSize;
                _vertsMinorGrid[i].Position += _gridV * (0.1f * d - _gridSize);
                _vertsMinorGrid[i + 1].Position = -_gridU * _gridSize;
                _vertsMinorGrid[i + 1].Position += _gridV * (0.1f * d - _gridSize);

                _vertsMinorGrid[i].Color = Splino.Palette.splino01.ToArgb();
                _vertsMinorGrid[i + 1].Color = Splino.Palette.splino01.ToArgb();
            }

            // Major Grid = 
            // (_gridSize * 2 for negative side of origin
            // * 2 Verts per line + 2 verts for the 0 line)
            // * 2 for the lines in the opposite direction
            _vertsMajorGrid = new CustomVertex.PositionColored[_gridSize * 8 + 4];

            for (int i = 0; i < _gridSize * 4 + 2; i += 2)
            {
                float d = (float)i * 0.5f;

                _vertsMajorGrid[i].Position = _gridU * (d - _gridSize);
                _vertsMajorGrid[i].Position += _gridV * _gridSize;
                _vertsMajorGrid[i + 1].Position = _gridU * (d - _gridSize);
                _vertsMajorGrid[i + 1].Position += -_gridV * _gridSize;

                _vertsMajorGrid[i].Color = Splino.Palette.splino02.ToArgb();
                _vertsMajorGrid[i + 1].Color = Splino.Palette.splino02.ToArgb();
            }

            for (int i = _gridSize * 4 + 2; i < _gridSize * 8 + 4; i += 2)
            {
                float d = ((float)(i - (_gridSize * 4 + 2))) * 0.5f;

                _vertsMajorGrid[i].Position = _gridU * _gridSize;
                _vertsMajorGrid[i].Position += _gridV * (d - _gridSize);
                _vertsMajorGrid[i + 1].Position = -_gridU * _gridSize;
                _vertsMajorGrid[i + 1].Position += _gridV * (d - _gridSize);

                _vertsMajorGrid[i].Color = Splino.Palette.splino02.ToArgb();
                _vertsMajorGrid[i + 1].Color = Splino.Palette.splino02.ToArgb();
            }

            _vertsXAxis = new CustomVertex.PositionColored[2];
            _vertsXAxis[0].X = 0;
            _vertsXAxis[0].Y = 0;
            _vertsXAxis[0].Z = 0;
            _vertsXAxis[0].Color = Splino.Palette.splinoX.ToArgb();
            _vertsXAxis[1].X = _gridSize;
            _vertsXAxis[1].Y = 0;
            _vertsXAxis[1].Z = 0;
            _vertsXAxis[1].Color = Splino.Palette.splinoX.ToArgb();

            _vertsYAxis = new CustomVertex.PositionColored[2];
            _vertsYAxis[0].X = 0;
            _vertsYAxis[0].Y = 0;
            _vertsYAxis[0].Z = 0;
            _vertsYAxis[0].Color = Splino.Palette.splinoY.ToArgb();
            _vertsYAxis[1].X = 0;
            _vertsYAxis[1].Y = _gridSize;
            _vertsYAxis[1].Z = 0;
            _vertsYAxis[1].Color = Splino.Palette.splinoY.ToArgb();

            _vertsZAxis = new CustomVertex.PositionColored[2];
            _vertsZAxis[0].X = 0;
            _vertsZAxis[0].Y = 0;
            _vertsZAxis[0].Z = 0;
            _vertsZAxis[0].Color = Splino.Palette.splinoZ.ToArgb();
            _vertsZAxis[1].X = 0;
            _vertsZAxis[1].Y = 0;
            _vertsZAxis[1].Z = _gridSize;
            _vertsZAxis[1].Color = Splino.Palette.splinoZ.ToArgb();

            ResetGrid();
        }

        /// <summary>
        /// Resets the vertex buffers that make up the cut plane grid.
        /// </summary>
        public void ResetGrid()
        {
            if (_vbMinorGrid == null || _vbMinorGrid.Disposed)
            {
                _vbMinorGrid = new VertexBuffer(
                    DeviceManager.Instance.Device,
                    CustomVertex.PositionColored.SizeInBytes * (_gridSize * 80 + 4),
                    Usage.WriteOnly,
                    CustomVertex.PositionColored.Format,
                    Pool.Default);
            }

            if (_vbMajorGrid == null || _vbMajorGrid.Disposed)
            {
                _vbMajorGrid = new VertexBuffer(
                    DeviceManager.Instance.Device,
                    CustomVertex.PositionColored.SizeInBytes * (_gridSize * 8 + 4),
                    Usage.WriteOnly,
                    CustomVertex.PositionColored.Format,
                    Pool.Default);
            }

            if (_vbXAxis == null || _vbXAxis.Disposed)
            {
                _vbXAxis = new VertexBuffer(
                    DeviceManager.Instance.Device,
                    CustomVertex.PositionColored.SizeInBytes * 2,
                    Usage.WriteOnly,
                    CustomVertex.PositionColored.Format,
                    Pool.Default);
            }

            if (_vbYAxis == null || _vbYAxis.Disposed)
            {
                _vbYAxis = new VertexBuffer(
                    DeviceManager.Instance.Device,
                    CustomVertex.PositionColored.SizeInBytes * 2,
                    Usage.WriteOnly,
                    CustomVertex.PositionColored.Format,
                    Pool.Default);
            }

            if (_vbZAxis == null || _vbZAxis.Disposed)
            {
                _vbZAxis = new VertexBuffer(
                    DeviceManager.Instance.Device,
                    CustomVertex.PositionColored.SizeInBytes * 2,
                    Usage.WriteOnly,
                    CustomVertex.PositionColored.Format,
                    Pool.Default);
            }

            DataStream stream = _vbMinorGrid.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * _vertsMinorGrid.Length, LockFlags.None);
            stream.WriteRange(_vertsMinorGrid);
            _vbMinorGrid.Unlock();

            stream = _vbMajorGrid.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * _vertsMajorGrid.Length, LockFlags.None);
            stream.WriteRange(_vertsMajorGrid);
            _vbMajorGrid.Unlock();

            stream = _vbXAxis.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * _vertsXAxis.Length, LockFlags.None);
            stream.WriteRange(_vertsXAxis);
            _vbXAxis.Unlock();

            stream = _vbYAxis.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * _vertsYAxis.Length, LockFlags.None);
            stream.WriteRange(_vertsYAxis);
            _vbYAxis.Unlock();

            stream = _vbZAxis.Lock(
                0, CustomVertex.PositionColored.SizeInBytes * _vertsZAxis.Length, LockFlags.None);
            stream.WriteRange(_vertsZAxis);
            _vbZAxis.Unlock();
        }

        /// <summary>
        /// Draws the minor grid lines, which are typically 1/10th the scale of the
        /// major grid lines and a dimmer color
        /// </summary>
        public void DrawMinorGrid()
        {
            DeviceManager.Instance.Device.SetStreamSource(
                0, _vbMinorGrid, 0, CustomVertex.PositionColored.SizeInBytes);
            //for (int i = 0; i < _gridSize * 80 + 4; i += 2)
            DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, (_gridSize * 80 + 4)/2);
        }

        /// <summary>
        /// Draws the major grid lines, which typically occur for every 1 unit and are
        /// brighter than the minor grid lines
        /// </summary>
        public void DrawMajorGrid()
        {
            DeviceManager.Instance.Device.SetStreamSource(
                0, _vbMajorGrid, 0, CustomVertex.PositionColored.SizeInBytes);
            for (int i = 0; i < _gridSize * 8 + 4; i += 2)
                DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, i, 1);
        }

        /// <summary>
        /// Determines which axes to draw based on the given viewport's cut plane/grid.
        /// </summary>
        public void DrawAxes()
        {
            if (_gridNormal.X == -1)
            {
                DrawYAxis();
                DrawZAxis();
            }
            else if (_gridNormal.Y == -1)
            {
                DrawXAxis();
                DrawZAxis();
            }
            else if (_gridNormal.Z == 1)
            {
                DrawXAxis();
                DrawYAxis();
            }
            else
                return;// Draw custom cut plane axes here!
        }

        /// <summary>
        /// Draws a line along the X axis
        /// </summary>
        private void DrawXAxis()
        {
            DeviceManager.Instance.Device.SetStreamSource(
                0, _vbXAxis, 0, CustomVertex.PositionColored.SizeInBytes);
            DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        }

        /// <summary>
        /// Draws a line along the Y axis
        /// </summary>
        private void DrawYAxis()
        {
            DeviceManager.Instance.Device.SetStreamSource(
                0, _vbYAxis, 0, CustomVertex.PositionColored.SizeInBytes);
            DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        }

        /// <summary>
        /// Draws a line along the Z axis
        /// </summary>
        private void DrawZAxis()
        {
            DeviceManager.Instance.Device.SetStreamSource(
                0, _vbZAxis, 0, CustomVertex.PositionColored.SizeInBytes);
            DeviceManager.Instance.Device.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        }

        /// <summary>
        /// Resizes this viewports swap chain to adjust to a viewport resize.
        /// </summary>
        public void ResizeSwapChain()
        {
            if (_swapChain != null)
                _swapChain.Dispose(); 
            if (_depthStencilBuffer != null)
                _depthStencilBuffer.Dispose();

            if (!Paused && ViewportWindow.PresentationPanel.Height != 0 &&
                ViewportWindow.PresentationPanel.Width != 0)
            {
                DeviceManager.Instance.PresentParameters.DeviceWindowHandle = ViewportWindow.PresentationPanel.Handle;
                DeviceManager.Instance.PresentParameters.BackBufferHeight = ViewportWindow.PresentationPanel.Height;
                DeviceManager.Instance.PresentParameters.BackBufferWidth = ViewportWindow.PresentationPanel.Width;
                _swapChain = new SwapChain(
                    DeviceManager.Instance.Device, 
                    DeviceManager.Instance.PresentParameters);

                _surfaceBackBuffer = _swapChain.GetBackBuffer(0);
                _depthStencilBuffer = Surface.CreateDepthStencil(
                    DeviceManager.Instance.Device,
                    ViewportWindow.PresentationPanel.Width,
                    ViewportWindow.PresentationPanel.Height,
                    Format.D16,
                    MultisampleType.None,
                    0,
                    false);

                UpdateDimensions();
            }

            DeviceManager.Instance.RequestRender();
        }
    }

    /// <summary>
    /// An enumerator to handle the draw order for the 
    /// axes orientation indicator lines, since they are
    /// rendered in a viewport with no z buffer.
    /// </summary>
    public enum AxesDrawOrder
    {
        /// <summary>Axes orientation indicator draw order.</summary>
        xyz,
        /// <summary>Axes orientation indicator draw order.</summary>
        xzy,
        /// <summary>Axes orientation indicator draw order.</summary>
        yxz,
        /// <summary>Axes orientation indicator draw order.</summary>
        yzx,
        /// <summary>Axes orientation indicator draw order.</summary>
        zxy,
        /// <summary>Axes orientation indicator draw order.</summary>
        zyx
    }
}
